using System;
using System.Collections.Generic;

using Atomic.Libraries;

namespace Atomic.Thermodynamics.StateEquations
{
	[Serializable]
	public class FittedPotentialBirchMurnaghan4 : FittedBirchMurnaghan4, IFittedStateEquationPotential
	{
		[NonSerialized] private RollingCache<double, Tuple<double, double, double, double>> values;
		private IGibbsPotential gibbsPotential;

		public FittedPotentialBirchMurnaghan4(double e0, double v0, double b0, double bp0, IEnumerable<IVolumePotential> potentials)
			: base(e0, v0, b0, bp0, CreatePoints(potentials, 0.0))
		{
			FittingPotentials = new ReadOnlyList<IVolumePotential>(potentials);
		}

		public static IEnumerable<IVolumeEnergyPoint> CreatePoints(IEnumerable<IVolumePotential> potentials, double temperature)
		{
			foreach (IVolumePotential potential in potentials)
			{
				yield return new VolumeEnergyPoint(potential.Volume, potential.Potential.FreeEnergy(temperature));
			}
		}

		public BirchMurnaghan4 StateEquation(double temperature)
		{
			if (temperature < 0.0)
			{
				throw new ArgumentOutOfRangeException();
			}

			if (values == null)
			{
				values = new RollingCache<double, Tuple<double, double, double, double>>(10000);
			}

			Tuple<double, double, double, double> value;
			if (!values.TryGetValue(temperature, out value))
			{
				double e0, v0, b0, bp0;
				BirchMurnaghan4.Fit(CreatePoints(FittingPotentials, temperature), out e0, out v0, out b0, out bp0);

				// Cache fitting values in a tuple.
				value = Tuple.Create<double, double, double, double>(e0, v0, b0, bp0);
				values.Add(temperature, value);
			}

			return new BirchMurnaghan4(value.Item1, value.Item2, value.Item3, value.Item4);
		}

		IStateEquation IStateEquationPotential.StateEquation(double temperature)
		{
			return StateEquation(temperature);
		}

		public double FreeEnergy(double temperature, double volume)
		{
			if (temperature == 0.0)
			{
				return Energy(volume);
			}

			return StateEquation(temperature).Energy(volume);
		}

		public double Pressure(double temperature, double volume)
		{
			if (temperature == 0.0)
			{
				return Pressure(volume);
			}

			return StateEquation(temperature).Pressure(volume);
		}

		public double PressureTemperatureDerivative(double temperature, double volume)
		{
			// How to compute this mixed derivative?
			Console.WriteLine("FIXME numerical derivative");
			double h = 0.1;
			return (Pressure(temperature + h, volume) - Pressure(temperature, volume)) / h;
			//throw new NotImplementedException();
		}

		public double BulkModulus(double temperature, double volume)
		{
			if (temperature < 0.0)
			{
				throw new ArgumentOutOfRangeException();
			}

			if (temperature == 0.0)
			{
				return BulkModulus(volume);
			}

			return StateEquation(temperature).BulkModulus(volume);
		}

		double IHelmholtzPotential.EquilibriumVolume(double temperature, double pressure)
		{
			if (pressure != 0.0)
			{
				throw new NotImplementedException();
			}

			return StateEquation(temperature).EquilibriumVolume;
		}

		public ReadOnlyList<IVolumePotential> FittingPotentials
		{
			get;
			private set;
		}

		IEnumerable<IVolumePotential> IFittedStateEquationPotential.FittingPotentials
		{
			get
			{
				return FittingPotentials;
			}
		}

		public IGibbsPotential GibbsPotential
		{
			get
			{
				if (gibbsPotential == null)
				{
					// Proper implementation?
					//gibbsPotential = HelmholtzPotential.Add(this).GibbsPotential;

					gibbsPotential = Atomic.Thermodynamics.GibbsPotential.StateEquationGibbsPotential(this);
				}

				return gibbsPotential;
			}
		}
	}
}
